package besta.moon.sql;

import java.sql.SQLException;

import besta.moon.metadata.MoonResultSetMetaData;
import besta.moon.sql.datatype.Mutable;
import besta.moon.sql.datatype.MutableNumeric;
import besta.moon.sql.expression.ExpressionValue;
import besta.moon.sql.parser.SQLTokenizer;

public abstract class Expression implements Cloneable {

    public static final Expression NULL = new ExpressionValue(null, SQLTokenizer.NULL);

    final private int type;
    private String name; 
    private String alias;

    private Expression[] params;

    protected Expression(int type) {
        this.type = type;
    }

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public final String getName() {
        return name;
    }

    public final void setName(String name) {
        this.alias = this.name = name;
    }

    public final String getAlias() {
        return alias;
    }

    public final void setAlias(String alias) {
        this.alias = alias;
    }

    public void setParams(Expression[] params) {
        this.params = params;
    }

    public void setParamAt(Expression param, int idx) {
        params[idx] = param;
    }

    public final Expression[] getParams() {
        return params;
    }

    public void optimize() throws SQLException {
        if (params != null) {
            for (Expression param : params) {
                param.optimize();
            }
        }
    }

    @Override
    public boolean equals(Object expr) {
        if (!(expr instanceof Expression)) {
            return false;
        }
        if (((Expression) expr).type == type) {

            Expression[] p1 = ((Expression) expr).params;
            Expression[] p2 = params;

            if (p1 != null && p2 != null) {
                for (int i = 0; i < p1.length; i++) {
                    if (!p2[i].equals(p1[i])) {
                        return false;
                    }
                }
            }
            String name1 = ((Expression) expr).name;
            String name2 = name;
            if (name1.equals(name2)) {
                return true;
            }
            if (name1 == null) {
                return false;
            }
            if (name1.equalsIgnoreCase(name2)) {
                return true;
            }
        }
        return false;
    }

    public abstract boolean isNull() throws Exception;

    public abstract boolean getBoolean() throws Exception;

    public abstract int getInt() throws Exception;

    public abstract long getLong() throws Exception;

    public abstract float getFloat() throws Exception;

    public abstract double getDouble() throws Exception;

    public abstract long getMoney() throws Exception;

    public abstract MutableNumeric getNumeric() throws Exception;

    public abstract Object getObject() throws Exception;

    public final Object getApiObject() throws Exception {
        Object obj = getObject();
        if (obj instanceof Mutable mutable) {
            return mutable.getImmutableObject();
        }
        return obj;
    }

    public abstract String getString() throws Exception;

    public abstract byte[] getBytes() throws Exception;

    public abstract int getDataType();

    public final int getType() {
        return type;
    }

    public String getTableName() {
        return null;
    }

    public int getPrecision() {
        return MoonResultSetMetaData.getDataTypePrecision(getDataType(), -1);
    }

    public int getScale() {
        return getScale(getDataType());
    }

    public final static int getScale(int dataType) {
        return switch (dataType) {
            case SQLTokenizer.MONEY, SQLTokenizer.SMALLMONEY -> 4;
            case SQLTokenizer.TIMESTAMP -> 9;
            case SQLTokenizer.NUMERIC, SQLTokenizer.DECIMAL -> 38;
            default -> 0;
        };
    }

    public int getDisplaySize() {
        return MoonResultSetMetaData.getDisplaySize(getDataType(), getPrecision(), getScale());
    }

    public boolean isDefinitelyWritable() {
        return false;
    }

    public boolean isAutoIncrement() {
        return false;
    }

    public boolean isCaseSensitive() {
        return false;
    }

    public boolean isNullable() {
        return true;
    }

    public static final int VALUE = 1;
    public static final int NAME = 2;
    public static final int FUNCTION = 3;
    public static final int GROUP_BY = 11;
    public static final int COUNT = 12;
    public static final int SUM = 13;
    public static final int FIRST = 14;
    public static final int LAST = 15;
    public static final int MIN = 16;
    public static final int MAX = 17;
    public static final int GROUP_BEGIN = GROUP_BY;

}
